package com.gitee.feizns.quickstart.mybatis.flex.query;

import com.gitee.feizns.dynamic.Maps;
import com.gitee.feizns.dynamic.Objs;
import com.gitee.feizns.quickstart.domain.page.PageAo;
import com.gitee.feizns.quickstart.domain.page.PageRecordsVo;
import com.gitee.feizns.quickstart.domain.query.QueryAo;
import com.gitee.feizns.quickstart.jackson.Val;
import com.gitee.feizns.quickstart.validation.ValidationUtils;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.CollectionUtil;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 通用查询工具类
 * @author feizns
 * @since 2025/2/17
 */
public interface Query {

    /**
     * 获取queryWrapper的sql查询条件
     * @param queryWrapper {@link QueryWrapper}
     * @return {@link String }
     */
    static String buildNoSelectSql(QueryWrapper queryWrapper) {
        return DialectFactory.getDialect().buildNoSelectSql(queryWrapper);
    }

    /**
     * 获取queryWrapper的sql查询条件
     * @param queryWrapper {@link QueryWrapper}
     * @return {@link String }
     */
    static String buildWhereSql(QueryWrapper queryWrapper) {
        return queryWrapper.toSQL().replace("SELECT * FROM ", "");
    }

    /**
     * 支持表
     */
    Map<String, String> SupportedTables = new ConcurrentHashMap<>();

    /**
     * 注册支持表
     * @param tableAlias 表别名
     * @param tableName 表名称
     */
    static void registerSupportedTables(String tableAlias, String tableName) {
        SupportedTables.put(tableAlias, tableName);
    }

    /**
     * 分页参数
     * @param page 分页参数
     * @return {@link Page}<{@link T}>
     */
    static <T> Page<T> page(PageAo page) {
        return Page.of(page.getCurrent(), page.getSize());
    }

    /**
     * 分页参数
     * @param page 分页参数
     * @return {@link Page}<{@link T}>
     */
    static <T> PageRecordsVo<T> pageRecordsVo(Page<T> page) {
        return new MybatisFlexPageRecordsVoImpl<>(page);
    }

    /**
     * 任意类型条件
     * {@link Query#of(Map)}
     * @param conditional 有條件
     * @return {@link QueryWrapper}
     */
    static QueryWrapper of(Object conditional) {
        return of(QueryWrapper.create(), conditional);
    }

    /**
     * 任意类型条件
     * {@link Query#of(Map)}
     * @param conditionals 有條件
     * @return {@link QueryWrapper}
     */
    static QueryWrapper of(QueryWrapper queryWrapper, Object... conditionals) {
        for (Object conditional : conditionals) {
            Map<String, Object> params;
            if ( conditional instanceof QueryAo )
                params = ((QueryAo) conditional).params();
            else
                params = Val.of(conditional).mapValue();
            //解析参数
            of(queryWrapper, params);
        }
        return queryWrapper;
    }

    /**
     * 查询包装器
     * 新版查询支持
     * 字段内：（$eq、$!eq、$ne、$in、$!in、$gt、$ge、$!gt、$!ge、$lt、$le、$!lt、$!le、$regex、$json_contains）
     * 字段外：（$keyword、$and、$or、$not）
     * @return {@link QueryWrapper}
     */
    static QueryWrapper of(Map<String, Object> conditional) {
        return of(QueryWrapper.create(), conditional);
    }


    /**
     * 查询包装器
     * 新版查询支持
     * 字段内：（$eq、$!eq、$ne、$in、$!in、$gt、$ge、$!gt、$!ge、$lt、$le、$!lt、$!le、$regex、$json_contains）
     * 字段外：（$keyword、$and、$or、$not）
     * @return {@link QueryWrapper}
     */
    static QueryWrapper of(QueryWrapper queryWrapper, Map<String, Object> conditional) {
        //解析常规配置
        Config config = parseConfig(conditional);
        //构建查询
        buildQueryWrapper(config, queryWrapper, conditional);
        return queryWrapper;
    }

    /**
     * 构建查询包装器
     * 构建自定义查询条件
     * @param query 查询
     * @param data 查询数据
     */
    static void buildQueryWrapper(Config config, QueryWrapper query, Map<String, Object> data) {
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Val value = Val.of(entry.getValue());
            switch ( key ) {
                case "$select":
                    List<String> select = value.listValue(String.class).stream()
                            .map(column -> column(column, config))
                            .collect(Collectors.toList());
                    if ( !CollectionUtil.isEmpty(select) )
                        query.select(select.toArray(new String[]{}));
                    break;
                case "$from":
                    String tableAlias = value.textValue();
                    String tableName = SupportedTables.get(tableAlias);
                    if ( tableName != null )
                        query.from(tableName);
                    break;
                case "$and":
                    if ( value.isArray() ) {
                        for (Object o : (Collection<?>) value)
                            query.and(q -> { buildQueryWrapper(config, query, Val.of(o).mapValue()); });
                    } else if ( value.isObject() )
                        query.and(q -> { buildQueryWrapper(config, query, value.mapValue()); });
                    break;
                case "$or":
                    if ( value.isArray() ) {
                        for (Object o : (Collection<?>) value)
                            query.or(q -> { buildQueryWrapper(config, query, Val.of(o).mapValue()); });
                    } else if ( value.isObject() )
                        query.or(q -> { buildQueryWrapper(config, query, value.mapValue()); });
                    break;
                case "$not":
                    //如果是对象，新构建一个条件对象并取反
                    QueryWrapper notQw = QueryWrapper.create();
                    if ( value.isArray() ) {
                        for (Object o : (Collection<?>) value)
                            query.and(q -> { buildQueryWrapper(config, notQw, Val.of(o).mapValue()); });
                    } else if ( value.isObject() )
                        buildQueryWrapper(config, notQw, value.mapValue());
                    buildNot(query, notQw);
                    break;
                case "$order":
                    List<Order> order = value.listValue(Order.class);
                    order.forEach(o -> query.orderBy(column(o.getColumn(), config), o.isAsc()));
                    break;
                case "$asc":
                    value.listValue(String.class).stream().map(column -> column(column, config)).forEach(column -> query.orderBy(column, true));
                    break;
                case "$desc":
                    value.listValue(String.class).stream().map(column -> column(column, config)).forEach(column -> query.orderBy(column, false));
                    break;
                case "$group":
                    value.listValue(String.class).stream().map(column -> column(column, config)).forEach(query::groupBy);
                    break;
                case "$null":
                    value.listValue(String.class).stream().map(column -> column(column, config)).forEach(query::isNull);
                    break;
                case "$limit":
                    query.limit(value.intValue() == -1 ? Integer.MAX_VALUE : value.intValue());
                    break;
                case "$offset":
                    int limit = Val.of(data.getOrDefault("$size", data.get("$limit"))).intValue(FlexGlobalConfig.getDefaultConfig().getDefaultPageSize());
                    query.limit(value.intValue(), limit == -1 ? Integer.MAX_VALUE : limit);
                    break;
                case "$keyword":
                    //解析格式
                    Keyword keyword = value.asBean(Keyword.class);
                    //校验格式
                    ValidationUtils.validateIfFailsThenThrow(keyword);
                    //设置查询列
                    String columns = concatWs(config, keyword.getColumns());
                    //模糊匹配关键字
                    buildQueryWrapperOperator(columns, query, Maps.of(keyword.getOperator(), keyword.getValue()));
                    break;
                default:
                    //json列提取
                    if ( key.contains("$") ) {
                        //设置json提前查询条件
                        int jsonPathStartIndex = key.indexOf("$");
                        String column = column(key.substring(0, jsonPathStartIndex), config);
                        if ( StringUtils.hasText(column) && value.isObject() ) {
                            String jsonpath = key.substring(jsonPathStartIndex);
                            String jsonColumn = String.format("JSON_UNQUOTE(JSON_EXTRACT(%s, '%s'))", column, jsonpath);
                            buildQueryWrapperOperator(jsonColumn, query, value.mapValue());
                        }
                    }
                    //值是数组或是对象，则遍历处理
                    else if ( value.isObject() || value.isArray() ) {
                        value.listValue().stream().map(Val::of).map(Val::mapValue).forEach(map -> buildQueryWrapperOperator(column(key, config), query, map));
                    }
                    //默认运算符及忽略的字段处理
                    else if ( !config.ignoredFields.contains(key) && config.getDefaultOperator() != null )
                        buildQueryWrapperOperator(column(key, config), query, Maps.of(config.getDefaultOperator(), entry.getValue()));
                    break;
            }
        }
    }

    /**
     * 构建 所有取反条件
     * @param query 查询
     * @param notQuery not 查询
     */
    static void buildNot(QueryWrapper query, QueryWrapper notQuery) {
        if ( notQuery.hasCondition() ) {
            query.and(String.format("NOT (%s)", notQuery.toSQL()
                    .replace("SELECT ", "")
                    .replace("* ", "")
                    .replace("FROM ", "")
                    .replace("WHERE ", "")));
        }
    }

    /**
     * Build Query Wrapper 运算符
     */
    static void buildQueryWrapperOperator(String column, QueryWrapper query, Map<String, Object> data) {
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            switch ( key ) {
                case "$eq":
                    query.eq(column, value);
                    break;
                case "$ne":
                    toList(value).forEach(val -> query.ne(column, val));
                    break;
                case "$gt":
                    query.gt(column, value);
                    break;
                case "$ge":
                    query.ge(column, value);
                    break;
                case "$lt":
                    query.lt(column, value);
                    break;
                case "$le":
                    query.le(column, value);
                    break;
                case "$in":
                    query.in(column, Val.of(value).listValue());
                    break;
                case "$contains":
                    toList(value).forEach(val -> query.like(column, val));
                    break;
                case "$iContains":
                    toList(value).forEach(val -> query.where("UPPER(" + column +") LIKE UPPER(CONCAT('%', ?, '%'))", val));
                    break;
                case "$startWith":
                    toList(value).forEach(val -> query.likeLeft(column, val));
                    break;
                case "$endWith":
                    toList(value).forEach(val -> query.likeRight(column, val));
                    break;
                case "$between":
                    Val betweenVal = Val.of(Objs.toArray(value));
                    query.between(column, betweenVal.get(0).objectValue(), betweenVal.get(1).objectValue());
                    break;
                case "$regexp":
                    toList(value).forEach(val -> query.where(column + " REGEXP ?", val));
                    break;
                case "$jsonContains":
                    String path = Val.of(data.getOrDefault("$jsonContains.path", "$")).textValue();
                    toList(value).forEach(val -> query.and(String.format("JSON_CONTAINS(%s, CONCAT(?), '%s')", column, path), val));
                    break;
            }
        }
    }

    /**
     * 转换为List
     * @param args 参数
     * @return {@link List }<{@link ? }>
     */
    static List<Object> toList(Object... args) {
        return Val.of(Objs.toArray(args)).listValue();
    }

    /**
     * 列
     * @param column 列
     * @param config 配置
     * @return {@link String}
     */
    static String column(String column, Config config) {
        return config.fieldNameConversionStrategy.converter.apply(column);
    }

    /**
     * 连接
     * @param columns 列
     * @param config 配置
     * @return {@link String}
     */
    static String concatWs(Config config, String... columns) {
        return Arrays.stream(columns).map(column -> column(column, config)).collect(Collectors.joining(",", "CONCAT_WS(", ")"));
    }

    /**
     * 解析配置
     * @param conditional 有條件
     * @return {@link Config}
     */
    static Config parseConfig(Map<String, Object> conditional) {
        return Val.of(conditional.get("$config")).as(Config.class).orElse(Config.Default);
    }

}
